package org.tyemill.whitesands
{
import flash.events.ErrorEvent;
import flash.events.Event;
import flash.events.EventDispatcher;
import flash.events.StatusEvent;
import flash.utils.describeType;
import flash.utils.getDefinitionByName;

import mx.controls.Alert;

import summit.data.AccountToken;

public class TestHarness extends EventDispatcher
{
	public static var testContainers:Object = new Object();
	
	public var testList:Object;
	
	private var _runtimeContainers:Object;
	public var token:AccountToken;
	private var _error:ErrorEvent;
	private var _restItems:Array;
	private var _started:Boolean;
	
	
	public var selectedTests:Array;
	public var selectedEntry:Object;
	public var selectedContainer:TestContainer;
	public var openEntries:Array;
	public var openContainer:TestContainer;
	
	[Bindable]
	public var testArray:Array;
	
	public function TestHarness()
	{
		super();
		_started = false;		
	}
	
	public function runTests():void
	{
		if(!_started)
		{
			_started = true;
			
			if(_restItems) 
			{
				for each(var entry:Object in _restItems)
				{
					testArray[entry.index].result = "Not Started";	
				}
				_restItems = [];
				this.dispatchEvent(new StatusEvent(StatusEvent.STATUS, false,
				false, "Starting", "0"));
			}
		}
		
		// just to get going - run them all.
		if(selectedTests.length == 0) 
		{
			cleanTests();
			_started = false;
			return;
		}
		
		selectedEntry = selectedTests.pop();
		
		initializeTest();
	}
	
	private function cleanTests():void
	{
		if(openEntries.length == 0)
		{
			if(null == _error)
				dispatchEvent(new Event(Event.COMPLETE));
			else Alert.show("Clean up complete - after Error: " + 
			_error.text);
			
			return;
		}
		
		var entry:Object = openEntries.pop();
		openContainer = (entry.instance as TestContainer);
		
		trace("** cleanup -> " + entry.testContainerFriendlyName);
		
		openContainer.cleanUp();
	}
		
	private function runTest():void
	{
		var signal:int = 
		selectedContainer[selectedEntry.testName](new Object());
		
		if(TestContainer.SIGNAL_SYNC == signal) 
			selectedContainer_Complete(null);
	}
	
	private function initializeTest():void
	{
		if(!_runtimeContainers) _runtimeContainers = new Object();
			
		if(!_runtimeContainers.hasOwnProperty(selectedEntry.testContainer))
		{
			var aClass:Class = getDefinitionByName(selectedEntry.testContainer)
			as Class;
			
			// now now... we're cheating a little here because we know
			// that the test containers in question require an argument
			//
			// in real life - we'd need a way to give the testfactory
			// a way to ask the harness operator for some information
			// and then let the factory create the factory.
			//
			// or the calling program (SVT in this case) can do the right
			// thing here. 
			//
			// ah well..
			
			selectedContainer = new aClass(token);
			_runtimeContainers[selectedEntry.testContainer] = 
			selectedContainer;
			
			selectedContainer.addEventListener(
			StatusEvent.STATUS, selectedContainer_Status);
			selectedContainer.addEventListener(Event.COMPLETE,
			selectedContainer_Complete);
			selectedContainer.addEventListener(ErrorEvent.ERROR,
			selectedContainer_Error);
			
			if(!openEntries) openEntries = [];
			
			selectedEntry.instance = selectedContainer;
			
			openEntries.push(selectedEntry);
			
			trace("** initialized -> " + 
			selectedEntry.testContainerFriendlyName);
			
			selectedContainer.initialize();
		}
		else 
		{
			selectedContainer = 
			_runtimeContainers[selectedEntry.testContainer];
			
			selectedContainer_Status(new StatusEvent(StatusEvent.STATUS,
			false, false, TestContainer.STATUS_CODE_INIT, "complete"));
		}
	}
	
	/** Called in the event of an error **/
	private function selectedContainer_Error(e:ErrorEvent):void
	{
		_error = e;
		cleanTests();
	}
	
	/** Called when the container completes initialization. **/
	private function selectedContainer_Status(e:StatusEvent):void
	{
		if(e.code == TestContainer.STATUS_CODE_INIT) runTest();
		else cleanTests();
	}
	
	/** Called when an async test completes **/
	private function selectedContainer_Complete(e:Event):void
	{
		this.dispatchEvent(new StatusEvent(StatusEvent.STATUS, false, false,
		selectedEntry.index, "1"));
		
		
		trace("******************************* passed -> " + 
		selectedEntry.testContainerFriendlyName + "::" + 
		selectedEntry.testName);
		trace("**");
		
		testArray[selectedEntry.index].result = "passed";
		
		if(!_restItems) _restItems = [];
		
		_restItems.push(selectedEntry);
		
		runTests();
	}
	
	
	/** Finds tests in a test container 
	 * 
	 * <p>Tests are defined as methods that contain the substring 'Test' 
	 * and take a single Object as an argument.</p>
	 */
	public function inspectTests():void
	{
		if(testList) return;
		
		testList = new Object();
		testArray = [];
		
		for(var prop:String in testContainers)
		{
			var qualifiedName:String = testContainers[prop]; 
			
			var descr:XML = describeType(getDefinitionByName(qualifiedName));
			
			for each(var method:XML in descr.factory.children())
			{
				var localName:String = method.localName();
				
				if(localName != "method") continue;
				if((method.@name).search("Test") == -1) continue;
				if(method.children().length() != 1) continue;
				if(method.parameter.@type != "Object") continue;
				
				var entry:Object = new Object();
				entry.testName = method.@name.toString();
				entry.testContainer = qualifiedName;
				entry.testContainerFriendlyName = prop;
				entry.index = testArray.length;
				entry.result = "Not Started";
				
				
				testList[prop.toString() + "::" + 
				(method.@name).toString()] = entry; 
				
				testArray.push(entry);
			}
		}
	}
		
}
}